package net.xiaohanlin.smartmetric.config;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import net.xiaohanlin.smartmetric.config.MetricRegistryConfiguration.MetricClassConfiguration;

class SmartMetricConfigXmlReader implements SmartMetricConfigReader {

	private File file = null;

	SmartMetricConfigXmlReader(File file) {
		this.file = file;
	}

	@Override
	public SmartMetricConfiguration readConfiguration() {
		Document document = null;
		try {
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			document = db.parse(file);
			return parse(document.getDocumentElement());
		} catch (Exception e) {
			throw new RuntimeException("<<SmartMetricsConfigXmlReader>> cannnot parse xml file : " + file.getAbsolutePath(), e);

		}
	}

	private SmartMetricConfiguration parse(Element documentElement) throws IllegalAccessException, InvocationTargetException {
		SmartMetricConfiguration configuration = new SmartMetricConfiguration();
		parseScheduledExecutorServiceConfiguration(documentElement, configuration);
		parseReporterConfiguration(documentElement, configuration);
		parseMetricsRegistry(documentElement, configuration);
		return configuration;
	}

	private void parseMetricsRegistry(Element documentElement, SmartMetricConfiguration configuration) {
		List<Element> metricsRegistryElements = getElementsByTagName(documentElement, "MetricsRegistry");
		Map<String, MetricRegistryConfiguration> nameMetricsRegistryConfiguration = new HashMap<>(metricsRegistryElements.size());
		for (Element metricsRegistryElement : metricsRegistryElements) {
			String metricsRegistryName = metricsRegistryElement.getAttribute("name");
			String prefix = metricsRegistryElement.getAttribute("prefix");
			String suffix = metricsRegistryElement.getAttribute("suffix");
			if (metricsRegistryName == null) {
				throw new ExceptionInInitializerError("MetricsRegistry name is null ");
			}
			if (nameMetricsRegistryConfiguration.containsKey(metricsRegistryName)) {
				throw new ExceptionInInitializerError("Duplicate MetricsRegistry name : " + metricsRegistryName);
			}

			MetricRegistryConfiguration metricsRegistry = new MetricRegistryConfiguration(metricsRegistryName, prefix, suffix);
			for (Element metrics : getElements(metricsRegistryElement)) {
				String metricClass = metrics.getAttribute("class");
				if (StringUtils.isEmpty(metricClass)) {
					throw new ExceptionInInitializerError("Exist empty metrics , for MetricsRegistry : " + metricsRegistryName);
				}

				String metricPrefix = metrics.getAttribute("prefix");
				String metricSuffix = metrics.getAttribute("suffix");
				MetricClassConfiguration mcc = new MetricClassConfiguration(metricClass, metricPrefix, metricSuffix);

				MetricClassConfiguration metricClassConfiguration = metricsRegistry.getMetricClassConfigs().get(metricClass);
				if (metricClassConfiguration != null) {
					throw new ExceptionInInitializerError("Duplicate metric class : " + metricClass);
				}
				metricsRegistry.addMetricClassConfiguration(mcc);
			}
			nameMetricsRegistryConfiguration.put(metricsRegistryName, metricsRegistry);

			List<MetricRegistryConfiguration> list = new ArrayList<>(nameMetricsRegistryConfiguration.size());
			list.addAll(nameMetricsRegistryConfiguration.values());
			configuration.setMetricRegistryList(list);
		}
	}

	private void parseReporterConfiguration(Element documentElement, SmartMetricConfiguration configuration) throws IllegalAccessException, InvocationTargetException {
		Element scheduledExecutorServiceEle = getElementByTagName(documentElement, "MetricsReporter");
		List<Element> elements = getElements(scheduledExecutorServiceEle);
		List<ReporterConfiguration> list = new ArrayList<>(elements.size());
		for (Element element : elements) {
			String tagName = element.getTagName();
			SmartReporterType smartReporterType = SmartReporterType.valueOf(tagName);
			if (smartReporterType == null) {
				throw new ExceptionInInitializerError("cannot find MetricsReporter for : " + tagName + ", support : [ " + StringUtils.join(SmartReporterType.values(), ",") + " ]");
			}
			ReporterConfiguration reporterConfiguration = null;
			switch (smartReporterType) {
				case console:
					reporterConfiguration = new ReporterConfiguration();
					reporterConfiguration.setSmartReporterType(SmartReporterType.console);
					break;
				case influxdb:
					reporterConfiguration = new InlfuxdbReporterConfiguration();
					break;

				default:
					break;
			}
			HashMap<String, String> attributesMap = getAttributesMap(element);
			BeanUtils.populate(reporterConfiguration, attributesMap);
			list.add(reporterConfiguration);
		}

		configuration.setdReporterConfiguration(list);
	}

	private void parseScheduledExecutorServiceConfiguration(Element documentElement, SmartMetricConfiguration configuration) throws IllegalAccessException, InvocationTargetException {
		Element scheduledExecutorServiceEle = getElementByTagName(documentElement, "ScheduledExecutorService");
		List<Element> elements = getElements(scheduledExecutorServiceEle);
		List<ScheduledExecutorServiceConfiguration> list = new ArrayList<>(elements.size());
		for (Element element : elements) {
			HashMap<String, String> attributesMap = getAttributesMap(element);
			ScheduledExecutorServiceConfiguration scheduledExecutorServiceConfiguration = new ScheduledExecutorServiceConfiguration();
			BeanUtils.populate(scheduledExecutorServiceConfiguration, attributesMap);
			for (ScheduledExecutorServiceConfiguration config : list) {
				if (config.getId().equalsIgnoreCase(scheduledExecutorServiceConfiguration.getId())) {
					throw new ExceptionInInitializerError("Duplicate ScheduledExecutorService id :" + config.getId());
				}
			}

			list.add(scheduledExecutorServiceConfiguration);
		}
		configuration.setScheduledExecutorServiceList(list);
	}

	public static List<Element> getElementsByTagName(Element element, String name) {
		List<Element> elementList = new ArrayList<Element>();
		NodeList nodeList = element.getElementsByTagName(name);
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node node = nodeList.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				elementList.add((Element) node);
			}
		}
		return elementList;
	}

	public static Element getElementByTagName(Element element, String name) {
		NodeList nodeList = element.getElementsByTagName(name);
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node node = nodeList.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				return (Element) node;
			}
		}
		return null;
	}

	public static Element getElement(Element element) {
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node node = nodeList.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				return (Element) node;
			}
		}
		return null;
	}

	public static List<Element> getElements(Element element) {
		List<Element> elementList = new ArrayList<Element>();
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node node = nodeList.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				elementList.add((Element) node);
			}
		}
		return elementList;
	}

	public static HashMap<String, String> getAttributesMap(Element element) {
		NamedNodeMap attributes = element.getAttributes();
		HashMap<String, String> map = new HashMap<String, String>(attributes.getLength());
		for (int i = 0; i < attributes.getLength(); i++) {
			Node item = attributes.item(i);
			map.put(item.getNodeName(), item.getNodeValue());
		}
		return map;
	}

}
